package main

import (
	"bufio"
	"context"
	"crypto/rand"
	"flag"
	"fmt"
	"github.com/ipfs/go-log/v2"
	"github.com/libp2p/go-libp2p"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	"github.com/libp2p/go-libp2p/core/crypto"
	"github.com/libp2p/go-libp2p/core/host"
	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/peer"
	drouting "github.com/libp2p/go-libp2p/p2p/discovery/routing"
	dutil "github.com/libp2p/go-libp2p/p2p/discovery/util"
	ma "github.com/multiformats/go-multiaddr"
	"os"
)

const PROTOCOL = "DHT/1.0"

const rendezvous = "rendezvous"

var logger = log.Logger("rendezvous")

type blankValidator struct{}

func (blankValidator) Validate(_ string, _ []byte) error        { return nil }
func (blankValidator) Select(_ string, _ [][]byte) (int, error) { return 0, nil }

func main() {
	log.SetAllLoggers(log.LevelError)
	err := log.SetLogLevel("rendezvous", "info")
	if err != nil {
		return
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	port := flag.Int("p", 0, "wait for incoming connections")
	target := flag.String("t", "", "target peer to dial")
	flag.Parse()

	r := rand.Reader

	// 生成私钥
	privateKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
	if err != nil {
		logger.Fatal(err)
	}

	opts := []libp2p.Option{
		libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", *port)),
		libp2p.Identity(privateKey),
		libp2p.DisableRelay(),
		libp2p.ForceReachabilityPublic(),
	}

	opts = append(opts, libp2p.NoSecurity)

	//初始化host,创建节点
	localPeer, err := libp2p.New(opts...)
	if err != nil {
		logger.Fatal(err)
	}

	//打印host地址
	fullAddr := getHostAddress(localPeer)
	logger.Infof("I am %s\n", fullAddr)
	// 设置流handler和自己定义的协议名称
	localPeer.SetStreamHandler(PROTOCOL, handleStream)
	logger.Info("listening for connections")
	logger.Infof("Now run \"go run main.go -p %d -t %s\" on a different terminal", *port+1, fullAddr)

	//使用DHT
	kademliaDHT, err := dht.New(ctx, localPeer)
	if err != nil {
		panic(err)
	}
	//把验证先去掉
	kademliaDHT.Validator = &blankValidator{}
	err = kademliaDHT.Bootstrap(ctx)
	if err != nil {
		panic(err)
	}

	if *target != "" {
		//连接到指定节点
		// 解析地址
		maddr, err := ma.NewMultiaddr(*target)
		if err != nil {
			logger.Fatal(err)
		}
		// 获得target节点id
		info, err := peer.AddrInfoFromP2pAddr(maddr)
		if err != nil {
			logger.Fatal(err)
		}
		if err := localPeer.Connect(ctx, *info); err != nil {
			logger.Fatal(err)
		} else {
			logger.Infoln("Connection established with bootstrap node:", *info)
		}
	}

	routingDiscovery := drouting.NewRoutingDiscovery(kademliaDHT)
	dutil.Advertise(ctx, routingDiscovery, rendezvous)
	peerChan, err := routingDiscovery.FindPeers(ctx, rendezvous)
	if err != nil {
		panic(err)
	}

	for p := range peerChan {
		if p.ID == localPeer.ID() {
			continue
		}
		logger.Infoln("Found peer:", p)
		err := localPeer.Connect(ctx, p)
		if err != nil {
			logger.Infoln("Connection failed:", err)
			continue
		}
		logger.Infoln("Connected to:", p)
	}

	go handleConsole(localPeer, kademliaDHT, ctx)

	<-ctx.Done()
}

// 控制台
func handleConsole(localPeer host.Host, kademliaDHT *dht.IpfsDHT, ctx context.Context) {
	//接收控制台输入
	stdReader := bufio.NewReader(os.Stdin)
	for {
		fmt.Print("> ")
		data, err := stdReader.ReadString('\n')
		if err != nil {
			logger.Errorln(err)
			return
		}
		if data != "" && data != "\n" && data != "\r\n" {
			data = data[:len(data)-2]
			//查看peer和DHT路由表
			if data == "info" {
				for i, peerId := range kademliaDHT.RoutingTable().ListPeers() {
					peerAddrInfo := localPeer.Peerstore().PeerInfo(peerId)
					logger.Infof("RoutingTable.ListPeers[%x]:%s", i, peerAddrInfo.String())
				}
			}

			//刷新peer和DHT路由表
			if data == "refresh" {
				kademliaDHT.RefreshRoutingTable()
				logger.Info("Refresh Routing Table success")
			}

			//存数据
			if data == "put" {
				key, value := "123", "abc"
				peers, err := kademliaDHT.GetClosestPeers(ctx, key)
				if err != nil {
					logger.Error(err)
				}
				//保存数据时，会根据key，存到最近的k个节点，也会本地存一份
				for i, p := range peers {
					peerAddrInfo := localPeer.Peerstore().PeerInfo(p)
					logger.Infof("Find closest peers[%d],%s", i, peerAddrInfo.String())
				}
				logger.Info("Save:[%s]%s", key, value)
				err = kademliaDHT.PutValue(ctx, key, []byte(value))
				if err != nil {
					logger.Error(err)
				} else {
					logger.Infof("success")
				}
			}

			if data == "get" {
				data, err := kademliaDHT.GetValue(ctx, "123")
				if err != nil {
					logger.Error(err)
				} else {
					logger.Infof("%s", data)
				}
			}
		}
	}
}

// 处理流
func handleStream(s network.Stream) {

	//读流
	logger.Infoln("Got a new stream!")
	buf := bufio.NewReader(s)
	str, err := buf.ReadString('\n')
	if err != nil {
		_ = s.Close()
		return
	}
	logger.Infoln("receive: %s", str)

	//如果是ping,则回复pang
	if str == "ping\n" {
		//回复流
		_, err = s.Write([]byte("pang"))
		_ = s.CloseWrite()
		return
	}
	_, err = s.Write([]byte("done"))
}

// 获得host地址
func getHostAddress(ha host.Host) string {
	hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s", ha.ID().Pretty()))
	addr := ha.Addrs()[0]
	return addr.Encapsulate(hostAddr).String()
}
